<template>
  <div class="timeline-player">
    <div class="controls">
      <px-button class="player-btn-o" link @click="rewind"><Rewind /></px-button>
      <px-button v-show="!playing" class="player-btn" link @click="togglePlay"><Play /></px-button>
      <px-button v-show="playing" class="player-btn" link @click="togglePlay"><Pause /></px-button>
      <px-button class="player-btn-o" link @click="forward"><Forward /></px-button>
    </div>
    <div class="timeline">
      <div
        v-for="(item, index) in items"
        :key="index"
        class="timeline-segment"
        :style="segmentStyles[index]"
        @mousemove="hoverIndex = index"
        @mouseleave="hoverIndex = null"
      >
        <div :class="['segment-label', { active: currentItemIndex === index }]">
          <px-tooltip v-if="hoverIndex === index" :content="item.label" placement="top">
            <div class="tooltip-area" />
          </px-tooltip>
        </div>
        <div v-if="loading && currentItemIndex === index" class="loading-indicator">
          <px-icon :component="Loading" />
        </div>
      </div>
      <div v-for="(item, index) in items.slice(0, -1)" :key="'divider-' + index" class="timeline-divider" :style="dividerStyle(index)" />
      <div class="current-time-indicator" :style="currentIndicatorStyle" />
    </div>
  </div>
</template>

<script setup>
import { ref, computed, watch, nextTick } from "vue";
import { PxIcon } from "px-ui";

import { Loading } from "@px-ui/icons-vue";
import Play from "../common/icons/play.svg?component";
import Pause from "../common/icons/pause.svg?component";
import Forward from "../common/icons/forward.svg?component";
import Rewind from "../common/icons/rewind.svg?component";

const props = defineProps({
  items: { type: Array, required: true },
  loop: { type: Boolean, default: true }
});

const currentTime = defineModel("currentTime", { default: 0 });
const playing = defineModel("playing", { default: false });

const emit = defineEmits(["item-finished"]);

const hoverIndex = ref(null);
let animationFrameId = null;
let startTimestamp = 0;
let baseTime = 0;
const loading = ref(false);

const totalDuration = computed(() => props.items.reduce((sum, item) => sum + item.duration, 0));

const currentPercent = computed(() => (currentTime.value / totalDuration.value) * 100);

const segmentStyles = computed(() => {
  const styles = [];
  let accumulated = 0;

  for (let i = 0; i < props.items.length; i++) {
    const item = props.items[i];
    const widthPercent = (item.duration / totalDuration.value) * 100;
    const startPercent = (accumulated / totalDuration.value) * 100;
    const endPercent = startPercent + widthPercent;

    let background = "#EBEDF0";
    if (currentPercent.value > startPercent) {
      const progress = Math.min((currentPercent.value - startPercent) / (endPercent - startPercent), 1);
      const progressWidth = progress * 100;
      background = `linear-gradient(to right, #C6E2FF ${progressWidth}%, #EBEDF0 ${progressWidth}%)`;
    }

    styles.push({
      width: `${widthPercent}%`,
      background,
      position: "relative"
    });

    accumulated += item.duration;
  }

  return styles;
});

const dividerStyle = index => {
  const offset = props.items.slice(0, index + 1).reduce((sum, item) => sum + item.duration, 0);
  const percent = (offset / totalDuration.value) * 100;
  return {
    left: `${percent}%`
  };
};

const currentIndicatorStyle = computed(() => {
  return {
    left: `${currentPercent.value}%`,
    transform: "translateX(-50%)",
    transition: playing.value ? "left 0.05s linear" : "none"
  };
});

const stopAnimation = () => {
  if (animationFrameId !== null) {
    cancelAnimationFrame(animationFrameId);
    animationFrameId = null;
  }
};

const getItemStart = index => props.items.slice(0, index).reduce((sum, item) => sum + item.duration, 0);
const getCurrentItem = () => props.items[currentItemIndex.value] || { duration: 0 };

const currentItemIndex = computed(() => {
  let acc = 0;
  for (let i = 0; i < props.items.length; i++) {
    acc += props.items[i].duration;
    if (currentTime.value < acc) return i;
  }
  return props.items.length - 1;
});

const startAnimation = () => {
  nextTick(() => {
    startTimestamp = performance.now();
    baseTime = currentTime.value;

    const step = now => {
      const elapsed = (now - startTimestamp) / 1000;
      let newTime = baseTime + elapsed;

      const currentItem = getCurrentItem();
      const itemStartTime = getItemStart(currentItemIndex.value);
      const itemEndTime = itemStartTime + currentItem.duration;

      if (newTime >= totalDuration.value) {
        if (props.loop) {
          newTime = 0;
          startTimestamp = performance.now();
          baseTime = 0;
        } else {
          currentTime.value = totalDuration.value;
          playing.value = false;
          stopAnimation();
          return;
        }
      }

      if (newTime >= itemEndTime) {
        currentTime.value = itemEndTime;
        stopAnimation();
        playing.value = false;
        loading.value = true;
        emit("item-finished", currentItemIndex.value);
        return;
      }

      currentTime.value = +newTime.toFixed(2);

      if (playing.value) {
        animationFrameId = requestAnimationFrame(step);
      }
    };

    animationFrameId = requestAnimationFrame(step);
  });
};

const togglePlay = () => {
  if (loading.value) return;
  playing.value = !playing.value;
  nextTick(() => {
    if (playing.value) {
      startAnimation();
    } else {
      stopAnimation();
    }
  });
};

const seekTo = time => {
  stopAnimation();
  currentTime.value = Math.max(0, Math.min(time, totalDuration.value));
  nextTick(() => {
    if (playing.value) startAnimation();
  });
};

const rewind = () => {
  const index = currentItemIndex.value;
  const prevIndex = Math.max(0, index - 1);
  seekTo(getItemStart(prevIndex));
};

const forward = () => {
  const index = currentItemIndex.value;
  const nextIndex = Math.min(props.items.length - 1, index + 1);
  seekTo(getItemStart(nextIndex));
};

watch(
  () => currentTime.value,
  () => {
    if (currentTime.value >= totalDuration.value && !props.loop) {
      playing.value = false;
      stopAnimation();
    }
  }
);

const resume = () => {
  if (!loading.value) return;
  loading.value = false;
  if (!playing.value) {
    playing.value = true;
    startAnimation();
  }
};

defineExpose({ resume });
</script>

<style scoped>
.timeline-player {
  position: absolute;
  bottom: 16px;
  left: 16px;
  display: flex;
  gap: 10px;
  align-items: center;
  width: 1294px;
  height: 40px;
  padding-right: 16px;
  padding-left: 16px;
  background: #fff;
  border-radius: 2px;
}

.controls {
  display: flex;
  width: 60px;
  margin-right: 12px;
}

.player-btn-o {
  width: 20px;
  height: 20px;
  margin-left: 0 !important;
}

.player-btn {
  width: 20px;
  height: 20px;
  margin-right: 5px;
  margin-left: 5px !important;
}

.timeline {
  position: relative;
  display: flex;
  flex: 1;
  align-items: center;
  height: 20px;
  overflow: hidden;
  background: #f5f5f5;
}

.timeline-segment {
  position: relative;
  height: 20px;
}

.timeline-divider {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 1;
  width: 1px;
  background: #fff;
}

.tooltip-area {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.current-time-indicator {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 2;
  width: 2px;
  background-color: #3b82f6;
  will-change: left;
}

.segment-label.active {
  font-weight: bold;
  color: #3b82f6;
}

.loading-indicator {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
</style>
